#ifndef MICROBE_H
#define MICROBE_H

#include <QPoint>

// Do this so we can use M_PI
#define _USE_MATH_DEFINES

#include <math.h>

#define START_NUTRIENT_MASS 0.3
#define START_CELLWALL 0x80
#define START_ACCEL 1000
#define START_GROWTH_RATE 0.3
#define START_TOXIN_RATE 0.2
#define START_STATIC_NUTRIENT_LOSS 0.01
#define START_STATIC_TOXIN_LOSS 0.1
#define START_NUTRIENT_MASS_DEATH 0.1
#define START_TOXIN_RATIO_DEATH 0.8
#define MAX_DIAMETER 17
#define MAX_RADIUS 8.5

/**
 * The number of pixels to check on the microbe's perimiter.
 */
#define PIXELS_TO_CHECK 8

/**
 * Basically since we suck out a value from 0.0 - 1.0, we need to know when to
 * subtract from the environment. Because the graphics hold values from 0 - 255 INTEGER,
 * we have to wait until a threshold is reached before we can actually draw
 * a circle subtracting values from the environment. Otherwise we might never
 * actually subtract anything because the threshold is smaller than 1 INTEGER.
 */
#define ABSORPTION_RENDER_THRESHOLD 0.05

class Microbe
{
public:
    //---------------------------------------------------------------------
    //
    //  Variables
    //
    //---------------------------------------------------------------------
    /**
     * The id of this microbe.
     */
    int id;

    /**
     * These are all the properties that can evolve on this microbe.
     *
     * Basically evolution change will be a random number between inc and dec and will never
     * exceed max and never fall below min.
     *
     * Every time an evolution occurs, one property will increase and another will decrease,
     * creating a system of trade-offs.
     *
     * Type: Evolver
     */
    //Evolver **evolvers;

    /**
     * The amount of mass that is gained every second in a perfect nutrient solution.
     */
    float growthRate; // 0 - 1.0

    /**
     * The amount of toxins taken in in one second in a pure toxin solution.
     */
    float toxinRate; // 0 - 1.0

    /**
     * The amount of nutrients the cell needs to maintain its size and not shrink.
     */
    float staticNutrientLoss;

    /**
     * The amount of toxins lost per second when the cell is doing nothing.
     */
    float staticToxinLoss;

    /**
     * If mass goes below this point the cell dies.
     */
    float nutrientMassDeath;

    /**
     * When the ratio of toxins to nutrients exceeds this threshold, the cell dies.
     */
    float toxinRatioDeath;

    /**
     * The amount of nutrients the microbe has sucked out of its environment.
     * Basically every time this hits a certain amount, we subtract some of the green
     * from the nutrients layer and reset this.
     */
    float nutrientsAbsorbedCounter;

    /**
     * The amount of toxins the microbe has sucked out of its environment.
     * Basically every time this hits a certain amount, we subtract some of the green
     * from the nutrients layer and reset this.
     */
    float toxinsAbsorbedCounter;

    /**
     * 0 - 1.0. Resistance to Toxicity: thickness of the cell wall.
     * Amount of toxic entry to cell is determined by difference between toxicity of environment and
     * cellWall.
     */
    float cellWall;

    /**
     * 0 - 1.0. Acceleration of the cell.
     * Acceleration determines how fast the cell can move through its environment
     */
    float accel;

    float x;
    float y;

    float speedX;
    float speedY;

    bool visible;
    bool mitosis;
    bool selected;

    QPoint destination;

    //---------------------------------------------------------------------
    //
    //  Properties
    //
    //---------------------------------------------------------------------
    //-----------------------------
    // toxinMass
    //-----------------------------
    /**
     * @private
     */
    float _toxinMass;

    /**
     * @default 0
     */
    float getToxinMass()
    {
        return _toxinMass;
    }

    /**
     * @private
     */
    void setToxinMass(float value)
    {
        if (value == _toxinMass)
        {
            return;
        }

        _toxinMass = value;

        if (_toxinMass < 0.0)
            _toxinMass = 0.0;
        if (_toxinMass > 1.0)
            _toxinMass = 1.0;
    }

    //-----------------------------
    // nutrientMass
    //-----------------------------
    /**
     * @private
     */
    float _nutrientMass;

    /**
     * @default 0
     */
    float getNutrientMass()
    {
        return _nutrientMass;
    }

    /**
     * @private
     */
    void setNutrientMass(float value) : void
    {
        if (value == _nutrientMass)
        {
            return;
        }

        _nutrientMass = value;

        if (_nutrientMass < 0.0)
            _nutrientMass = 0.0;
        if (_nutrientMass > 1.0)
            _nutrientMass = 1.0;
    }

    //-----------------------------
    // mass
    //-----------------------------
    float getMass()
    {
        return (getToxinMass() + getNutrientMass()) / 2.0;
    }

    //-----------------------------
    // toxinsToNutrients
    //-----------------------------
    // The ratio of toxins to nutrients in this cell. 1.0 = all toxins. 0.0 = all nutrients.
    float getToxinsToNutrients()
    {
        return getToxinMass() / getNutrientMass();
    }

    float getRadius()
    {
        return sqrt(getMass() / Math.PI) * MAX_RADIUS;
    }

    float getDiameter()
    {
        return getRadius() * 2;
    }

    //---------------------------------------------------------------------
    //
    //  Constructor
    //
    //---------------------------------------------------------------------
    /**
     *  Constructor
     */
    Microbe(int _id = 0) : id(_id), nutrientMassDeath(START_NUTRIENT_MASS_DEATH), growthRate(START_GROWTH_RATE), toxinRate(START_TOXIN_RATE),
        staticNutrientLoss(START_STATIC_NUTRIENT_LOSS), staticToxinLoss(START_STATIC_TOXIN_LOSS), toxinRatioDeath(START_TOXIN_RATIO_DEATH),
        nutrientsAbsorbedCounter(0), toxinsAbsorbedCounter(0), cellWall(START_CELLWALL), accel(START_ACCEL), x(0), y(0), speedX(0), speedY(0),
        visible(true), mitosis(false), selected(false),destination(NULL), _toxinMass(0), _nutrientMass(START_NUTRIENT_MASS)
    {
        //evolvers.push(new NumberEvolver(this, "accel", 5.0, -4.0, 100, 10, 0.5));
    }

    //---------------------------------------------------------------------
    //
    //  Methods
    //
    //---------------------------------------------------------------------
    void resetRender()
    {
        //bd = new BitmapData(MAX_DIAMETER, MAX_DIAMETER, true, 0x00000000);
    }

    void resetPhysics()
    {
        speedX = 0;
        speedY = 0;
    }

    void update(float timeElapsed, Environment *environment)
    {
        float accelX = 0;
        float accelY = 0;

        if (mitosis)
        {
            return;
        }

        float angle = 0;
        Point *pixelsToCheck = new Point[PIXELS_TO_CHECK];
        Point *p;
        int iMaxNutrients = -1;
        float maxNutrients = 0;

        for (int i = 0; i < PIXELS_TO_CHECK; i++)
        {
            p = new Point();

            p->x = x + cos(angle) * (getRadius() / 2.0);
            p->y = y + sin(angle) * (getRadius() / 2.0);

            pixelsToCheck[i] = p;

            angle += Math.PI / 4;
        }

        float avgTox = 0.0;
        float avgNut = 0.0;
        float avgFlu = 0.0;

        for (i = 0; i < PIXELS_TO_CHECK; i++)
        {
            //var pix : uint = environment.bdToxic.getPixel(pixelsToCheck[i].x, pixelsToCheck[i].y);
            //avgTox += (pix >> 16) & 0xFF;

            //pix = environment.bdNutrients.getPixel(pixelsToCheck[i].x, pixelsToCheck[i].y);
            //var nut : Number = uint((pix >> 8) & 0xFF);
            //avgNut += nut;

            //pix = environment.bdFluidity.getPixel(pixelsToCheck[i].x, pixelsToCheck[i].y);
            //avgFlu += (pix) & 0xFF;

            //if (nut > maxNutrients)
            //{
            //    maxNutrients = nut;
           //     iMaxNutrients = i;
            //}
        }

        if (!destination)
        {
            // Let's calculate movement based on the direction that leads to the most food.
            if (iMaxNutrients != -1)
            {
                accelX += cos((Math.PI / 4) * iMaxNutrients) * accel;
                accelY += sin((Math.PI / 4) * iMaxNutrients) * accel;
            }
        }
        else
        {
            float xDir = destination->x - x;
            float yDir = destination->y - y;

            float len = sqrt(xDir * xDir + yDir * yDir);
            xDir /= len;
            yDir /= len;

            accelX += xDir * accel;
            accelY += yDir * accel;
        }

        speedX += accelX * timeElapsed;
        speedY += accelY * timeElapsed;

        x += speedX * timeElapsed;
        y += speedY * timeElapsed;

        if (destination)
        {
            if (Math.abs(x - destination->x) < 5 && Math.abs(y - destination->y) < 5)
            {
                destination = NULL;
            }
        }

        avgTox = (avgTox / PIXELS_TO_CHECK) / 255.0;
        avgNut = (avgNut / PIXELS_TO_CHECK) / 255.0;
        avgFlu = (avgFlu / PIXELS_TO_CHECK) / 255.0;

        // Friction
        speedX -= speedX * avgFlu;
        speedY -= speedY * avgFlu;

        // An increase of 255 mass every 60 seconds
        float growthIntake = growthRate * timeElapsed * avgNut;
        float nutrientLoss = -staticNutrientLoss * timeElapsed;

        nutrientLoss -= (Math.abs(accelX) / accel) * timeElapsed * 0.05;
        nutrientLoss -= (Math.abs(accelY) / accel) * timeElapsed * 0.05;

        float nutrientMassChange = growthIntake + nutrientLoss;

        nutrientsAbsorbedCounter += growthIntake;

        setNutrientMass(getNutrientMass() + nutrientMassChange);

        float toxinIntake = toxinRate * timeElapsed * avgTox;
        float toxinLoss = staticToxinLoss * timeElapsed;

        toxinsAbsorbedCounter += toxinIntake * (1.0 - cellWall);
        toxinMass += (toxinIntake * (1.0 - cellWall)) - toxinLoss;

        if (toxinsAbsorbedCounter >= absorptionRenderThreshold)
        {
            //MicrobialLife.getInstance().environment.subtractToxins(x, y, radius * 3, toxinsAbsorbedCounter);

            toxinsAbsorbedCounter = 0;
        }

        if (toxinsToNutrients >= toxinRatioDeath)
        {
            //MicrobialLife.getInstance().microbesToSplice.push(this);
        }

        if (nutrientsAbsorbedCounter > absorptionRenderThreshold)
        {
            //MicrobialLife.getInstance().environment.subtractNutrients(x, y, radius * 3, nutrientsAbsorbedCounter);

            nutrientsAbsorbedCounter = 0;
        }

        if (getNutrientMass() >= 0.99)
        {
            //new Mitosis(this, splitSelf());
        }
        else if (getNutrientMass() < nutrientMassDeath)
        {
            //MicrobialLife.getInstance().microbesToSplice.push(this);
        }

        // Check bounds
        if (x < 0)
            x = 0;
        if (x > 800)
            x = 800;
        if (y < 0)
            y = 0;
        if (y > 600)
            y = 600;
    }

        Microbe *splitSelf()
        {
            Microbe *newMicrobe = new Microbe();

            newMicrobe->x = x;
            newMicrobe->y = y;
            newMicrobe->setToxinMass(toxinMass);
            newMicrobe->setNutrientMass(nutrientMass);
            newMicrobe->accel = accel;
            newMicrobe->cellWall = cellWall;
            newMicrobe->destination = destination;
            newMicrobe->growthRate = growthRate;
            newMicrobe->nutrientMassDeath = nutrientMassDeath;
            newMicrobe->staticNutrientLoss = staticNutrientLoss;
            newMicrobe->staticToxinLoss = staticToxinLoss;
            newMicrobe->toxinRate = toxinRate;

            return newMicrobe;
        }

        uint getFillColor()
        {
            uint fillColor = 0x0;

            // Red = cellWall.
            fillColor |= uint(toxinsToNutrients * 255) << 16;

            // Green = energy.
            fillColor |= uint((1.0 - toxinsToNutrients) * 255) << 8;

            // Blue = accel.
            fillColor |= accel;

            return fillColor;
        }

        void render()
        {

//                shape.graphics.clear();

//                shape.graphics.beginFill(0xFFFFFF, 1.0);
//                shape.graphics.drawCircle(0, 0, radius);
//                shape.graphics.endFill();

//                shape.graphics.beginFill(fillColor, 1.0);
//                shape.graphics.drawCircle(0, 0, radius - 1);
//                shape.graphics.endFill();

//                if (selected)
//                {
//                    shape.graphics.lineStyle(1, 0xFFFFFF, 0.8);
//                    shape.graphics.drawRect(-(radius + 2), -(radius + 2), diameter + 4, diameter + 4);
//                }



//                bd.fillRect(new Rectangle(0, 0, MAX_DIAMETER, MAX_DIAMETER), 0x000000);
//                bd.draw(shape, mat, null, null, null, false);
//                frame = 0;
        }
    }

#endif // MICROBE_H
